﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DirectCanvas;
using DirectCanvas.Misc;
using System.Diagnostics;
using DirectCanvas.Brushes;
using FarseerPhysics.Dynamics;
using DirectCanvas.Shapes;
using Blake.NUI.WPF.Utility;
using System.Windows.Threading;
using Microsoft.Xna.Framework;
using System.Threading.Tasks;
using FarseerPhysics.Factories;

namespace KinectBender
{
    class FarseerPhysicsScene : IPhysicsScene
    {
        #region Fields

        bool isRendering = false;

        Size particleSize;
        Size imageSize;
        DirectCanvasFactory factory;
        WPFPresenter presenter;
        DrawingLayer intermediateLayer;
        Brush particleBrush;
        Brush wallBrush;

        Body fountain;

        int frameCount;
        Stopwatch fpsTimer;

        World world;

        List<Particle> particles;
        #endregion

        #region Properties

        public bool IsFountainOn { get; set; }

        public double FPS { get; private set; }

        public bool IsDisposed { get; private set; }

        public System.Windows.Media.ImageSource ImageSource
        {
            get
            {
                return presenter.ImageSource;
            }
        }

        public int NumParticles
        {
            get
            {
                return particles.Count;
            }
        }

        #endregion

        #region Events

        public event EventHandler FPSUpdated;

        protected void OnFPSUpdated()
        {
            if (FPSUpdated == null)
                return;
            FPSUpdated(this, EventArgs.Empty);
        }

        #endregion

        #region Constructors

        public FarseerPhysicsScene(int width, int height)
        {
            IsDisposed = false;
            Init(new Size(width, height));
        }

        ~FarseerPhysicsScene()
        {
            Dispose();
        }

        #endregion

        #region Public Methods

        public void Start()
        {
            CompositionTargetEx.FrameUpdating += CompositionTargetEx_FrameUpdating;
        }

        public void Stop()
        {
            CompositionTargetEx.FrameUpdating -= CompositionTargetEx_FrameUpdating;
        }

        #endregion

        #region Private Methods

        void CountFrames()
        {
            frameCount++;
            if (fpsTimer == null)
            {
                fpsTimer = new Stopwatch();
                fpsTimer.Start();
            }

            if (fpsTimer.Elapsed.TotalSeconds > 1)
            {
                FPS = frameCount / fpsTimer.Elapsed.TotalSeconds;
                OnFPSUpdated();
                fpsTimer.Restart();
                frameCount = 0;
            }
        }

        void Init(Size imageSize)
        {
            particleSize = new Size(10, 10);
            this.imageSize = imageSize;
            world = new World(new Microsoft.Xna.Framework.Vector2(0f, -10f));

            CreateBorder();
            CreateFountain();

            factory = new DirectCanvasFactory();
            presenter = new WPFPresenter(factory, imageSize.Width, imageSize.Height);
            intermediateLayer = factory.CreateDrawingLayer(particleSize.Width, particleSize.Height);

            particleBrush = factory.CreateSolidColorBrush(new DirectCanvas.Color4(.25f, 0.2f, 0.4f, 1f));
            wallBrush = factory.CreateSolidColorBrush(new Color4(1f, 0f, 1f, 0f));

            intermediateLayer.Clear(new DirectCanvas.Color4(0, 0, 0, 0));
            intermediateLayer.BeginDraw();

            Ellipse ellipse = new Ellipse(new PointF(particleSize.Width / 2, particleSize.Height / 2), particleSize.Width / 2, particleSize.Height / 2);
            intermediateLayer.FillEllipse(particleBrush, ellipse);
            intermediateLayer.EndDraw();

            particles = new List<Particle>();

            Random random = new Random();
            for (int i = 0; i < 1000; i++)
            {
                var radius = (float)MathUtility.MapValue(particleSize.Width, 0, imageSize.Width, 0, 10) * 1.0f;
                var particle = new Particle(world, radius);

                particle.Position = new Vector2((float)(random.NextDouble() * 10 - 5),
                                                (float)(random.NextDouble() * 10 - 5));

                particles.Add(particle);
            }
            var radius2 = (float)MathUtility.MapValue(particleSize.Width, 0, imageSize.Width, 0, 10) * 3.0f;
            var particle2 = new Particle(world, radius2);

            particle2.Position = new Vector2((float)(random.NextDouble() * 10 - 5),
                                            (float)(random.NextDouble() * 10 - 5));

            particles.Add(particle2);
            

        }

        private void CreateFountain()
        {
            fountain = BodyFactory.CreateRectangle(world, 1, 8, 1f, new Vector2(0, -6f));
            fountain.IsSensor = true;
            fountain.OnCollision += new OnCollisionEventHandler(fountain_OnCollision);
            BodyFactory.CreateRectangle(world, 1, 4, 1f, new Vector2(1, -5f));
            BodyFactory.CreateRectangle(world, 1, 4, 1f, new Vector2(-1, -5f));
        }

        bool fountain_OnCollision(Fixture fixtureA, Fixture fixtureB, FarseerPhysics.Dynamics.Contacts.Contact contact)
        {
            if (!IsFountainOn)
                return false;

            if (fixtureB.Body.LinearVelocity.Y < 13f)
                fixtureB.Body.ApplyForce(new Vector2(0f, 15f));

            return false;
        }

        private void CreateBorder()
        {
            BodyFactory.CreateRectangle(world, 30, 1, 1f, new Vector2(0, 10f));
            BodyFactory.CreateRectangle(world, 30, 1, 1f, new Vector2(0, -10f));

            BodyFactory.CreateRectangle(world, 1, 30, 1f, new Vector2(10f, 0));
            BodyFactory.CreateRectangle(world, 1, 30, 1f, new Vector2(-10f, 0));
        }

        void CompositionTargetEx_FrameUpdating(object sender, DeltaRenderingEventArgs e)
        {
            Step(e.Delta);
        }

        private void Step(TimeSpan delta)
        {
            Update(delta);
            Render();
            CountFrames();
        }

        private void Update(TimeSpan delta)
        {
            //    float factor = 50;
            Parallel.ForEach(particles, particle =>
            {

                //particle.Body.ApplyForce(new Vector2(-particle.Position.X / factor, -particle.Position.Y / factor));
                //particle.Body.ApplyForce(new Vector2(0, -10f));
                //particle.Body.ApplyTorque(4f);
            });
            world.Step((float)1f / 60f);

        }

        private void Render()
        {
            if (isRendering)
                return;
            isRendering = true;
            presenter.Clear(new DirectCanvas.Color4(1, 1, 1, 1));

            presenter.BeginDraw();

            DrawRectangle(new RectangleF(0, 10, 30, 1));
            DrawRectangle(new RectangleF(0, -10, 30, 1));
            DrawRectangle(new RectangleF(10, 0, 1, 30));
            DrawRectangle(new RectangleF(-10, 0, 1, 30));

            DrawRectangle(new RectangleF(1, -5, 1, 4));
            DrawRectangle(new RectangleF(-1, -5, 1, 4));

            presenter.EndDraw();

            presenter.BeginCompose();

            var sourceArea = new RectangleF(0, 0, particleSize.Width, particleSize.Height);
            var rot = new RotationParameters();
            var tint = new Color4(1, 1, 1, 1);
            foreach (var particle in particles)
            {
                var position = PositionToScreen(particle.Position);
                var destinationArea = new RectangleF(position.X - particleSize.Width / 2, position.Y - particleSize.Height / 2,
                                                    particleSize.Width, particleSize.Height);
                presenter.ComposeLayer(intermediateLayer, sourceArea, destinationArea, rot, tint);
            }

            presenter.EndCompose();
            presenter.Present();
            isRendering = false;
        }

        private void DrawRectangle(RectangleF rect)
        {
            RectangleF newRect = new RectangleF();
            newRect.Width = (float)MathUtility.MapValue(rect.Width, 0, 20, 0, imageSize.Width);
            newRect.Height = (float)MathUtility.MapValue(rect.Height, 0, 20, 0, imageSize.Height);
            newRect.X = (float)MathUtility.MapValue(rect.X, -10, 10, 0, imageSize.Width) - newRect.Width / 2;
            newRect.Y = (float)MathUtility.MapValue(rect.Y, 10, -10, 0, imageSize.Height) - newRect.Height / 2;
            presenter.FillRectangle(wallBrush, newRect);
        }

        private PointF PositionToScreen(Microsoft.Xna.Framework.Vector2 position)
        {
            float x = position.X;
            float y = position.Y;

            x = (float)MathUtility.MapValue(x, -10, 10, 0, imageSize.Width);
            y = (float)MathUtility.MapValue(y, 10, -10, 0, imageSize.Height);

            return new PointF(x, y);
        }

        #endregion

        public void Dispose()
        {
            if (IsDisposed)
                return;
            IsDisposed = true;

            if (intermediateLayer != null) { intermediateLayer.Dispose(); intermediateLayer = null; }
            if (intermediateLayer != null) { intermediateLayer.Dispose(); intermediateLayer = null; }
            if (particleBrush != null) { particleBrush.Dispose(); particleBrush = null; }
            if (wallBrush != null) { wallBrush.Dispose(); wallBrush = null; }
            if (presenter != null) { presenter.Dispose(); presenter = null; }
            if (factory != null) { factory.Dispose(); factory = null; }
        }

    }
}
